/*
 * Copyright (c) 2022, IMMORTA Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice, this list
 *   of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright notice, this
 *   list of conditions and the following disclaimer in the documentation and/or
 *   other materials provided with the distribution.
 *
 * - Neither the name of IMMORTA Inc. nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef GPIO_REG_ACCESS_H
#define GPIO_REG_ACCESS_H

/*!
 * @file gpio_reg_access.h
 * @brief This file declares or defines gpio register access functions
 */

/*******Includes***************************************************************/
#include "gpio_drv.h"

/*******Definitions************************************************************/
#define GPIO_PCR_PUM(x)      (((uint32_t)(((uint32_t)(x)) << GPIO_PCR_PUM_Pos)) & GPIO_PCR_PUM_Msk)
#define GPIO_PCR_SRC(x)      (((uint32_t)(((uint32_t)(x)) << GPIO_PCR_SRC_Pos)) & GPIO_PCR_SRC_Msk)
#define GPIO_PCR_SMT(x)      (((uint32_t)(((uint32_t)(x)) << GPIO_PCR_SMT_Pos)) & GPIO_PCR_SMT_Msk)
#define GPIO_PCR_DSC(x)      (((uint32_t)(((uint32_t)(x)) << GPIO_PCR_DSC_Pos)) & GPIO_PCR_DSC_Msk)
#define GPIO_PCR_PMS(x)      (((uint32_t)(((uint32_t)(x)) << GPIO_PCR_PMS_Pos)) & GPIO_PCR_PMS_Msk)
#define GPIO_PCR_MUX(x)      (((uint32_t)(((uint32_t)(x)) << GPIO_PCR_MUX_Pos)) & GPIO_PCR_MUX_Msk)
#define GPIO_PCR_IRQT(x)     (((uint32_t)(((uint32_t)(x)) << GPIO_PCR_IRQT_Pos)) & GPIO_PCR_IRQT_Msk)
#define GPIO_PCR_LKC(x)      (((uint32_t)(((uint32_t)(x)) << GPIO_PCR_LKC_Pos)) & GPIO_PCR_LKC_Msk)
#define GPIO_PCR_RMDFE(x)    (((uint32_t)(((uint32_t)(x)) << GPIO_PCR_RMDFE_Pos)) & GPIO_PCR_RMDFE_Msk)

#define GPIO_IRQF_IRQF(x)    (((uint32_t)(((uint32_t)(x)) << GPIO_IRQF_IRQF_Pos)) & GPIO_IRQF_IRQF_Msk)

#if (CONFIG_GPIO_SUPPORT_INDEPENDENT_DFL == 1U)
#define GPIO_DFL_RMDFL(x)      (((uint32_t)(((uint32_t)(x)) << GPIO_DFL_RMDFL_Pos)) & GPIO_DFL_RMDFL_Msk)
#define GPIO_DFL_LPDFL(x)      (((uint32_t)(((uint32_t)(x)) << GPIO_DFL_LPDFL_Pos)) & GPIO_DFL_LPDFL_Msk)
#else
#define GPIO_DFL_DFL(x)      (((uint32_t)(((uint32_t)(x)) << GPIO_DFL_DFL_Pos)) & GPIO_DFL_DFL_Msk)
#endif
#define GPIO_LPDFC_LPDFE(x)  (((uint32_t)(((uint32_t)(x)) << GPIO_LPDFC_LPDFE_Pos)) & GPIO_LPDFC_LPDFE_Msk)
#define GPIO_LPDFC_LPDFPS(x) (((uint32_t)(((uint32_t)(x)) << GPIO_LPDFC_LPDFPS_Pos)) & GPIO_LPDFC_LPDFPS_Msk)

/*******APIs*******************************************************************/
#if defined(__cplusplus)
extern "C" {
#endif

/*!
 * @brief Initializes the pins with the options provided in the structure
 *
 * @Note  This function only used to config the pin function as GPIO, If you
 *        want to config the pin function as nonGPIO, Please use GPIO_REG_SetPinFunc()
 *
 * @param[in] config: the configuration structure
 * @return None
 */
void GPIO_REG_Init(const gpio_pin_config_t* gpioConfig);

/*!
 * @brief Configures the internal resistor for the given pin
 *
 * @param[in] regBase: GPIO register base pointer
 * @param[in] pinNum: The pin number, Valid range:0 - 31
 * @param[in] pullConfig: internal resistor pull feature selection
 *        - GPIO_PIN_PULL_FLOATING: internal pull-down or pull-up resistor is not enabled.
 *        - GPIO_PIN_PULL_UP: internal pull-down resistor is enabled
 *        - GPIO_PIN_PULL_DOWN: internal pull-up resistor is enabled
 * @return None
 */
static inline void GPIO_REG_SetPinPull(GPIO_Type* regBase,
                                       uint32_t pinNum,
                                       gpio_pin_pull_t pullConfig)
{
    regBase->PCR[pinNum] = ((regBase->PCR[pinNum] & ~GPIO_PCR_PUM_Msk) | GPIO_PCR_PUM(pullConfig));
}

/*!
 * @brief Configure the mode for the given pin with the given mode
 *
 * @param[in] regBase: GPIO register base pointer
 * @param[in] pinNum: The pin number, Valid range:0 - 31
 * @param[in] pinMode: the pin mode:
 *        - GPIO_PIN_MODE_HIZ: corresponding pin is set to Hiz
 *        - GPIO_PIN_MODE_INPUT: corresponding pin is set to input
 *        - GPIO_PIN_MODE_OUTPUT: corresponding pin is set to output
 *        - GPIO_PIN_MODE_BI_OUTPUT: corresponding pin is set to bidirectional output(input and output)
 * @return None
 */
static inline void GPIO_REG_SetPinMode(GPIO_Type* regBase,
                                       uint32_t pinNum,
                                       gpio_pin_mode_t pinMode)
{
    regBase->PCR[pinNum] = ((regBase->PCR[pinNum] & ~GPIO_PCR_PMS_Msk) | GPIO_PCR_PMS(pinMode));
}

/*!
 * @brief Get the pin mode configuration for the given pin
 *
 * @param[in] regBase: GPIO register base pointer
 * @param[in] pinNum: The pin number, Valid range:0 - 31
 * @return GPIO pin mode.
 *        - GPIO_PIN_MODE_HIZ: corresponding pin is set to Hiz
 *        - GPIO_PIN_MODE_INPUT: corresponding pin is set to input
 *        - GPIO_PIN_MODE_OUTPUT: corresponding pin is set to output
 *        - GPIO_PIN_MODE_BI_OUTPUT: corresponding pin is set to bidirectional output(input and output)
 */
static inline gpio_pin_mode_t GPIO_REG_GetPinMode(const GPIO_Type* regBase,
                                                  uint32_t pinNum)
{
    return (gpio_pin_mode_t)((regBase->PCR[pinNum] & GPIO_PCR_PMS_Msk) >> GPIO_PCR_PMS_Pos);
}

/*!
 * @brief Configures the pin lock for the given pin
 *
 * @param[in] regBase: GPIO register base pointer
 * @param[in] pinNum: The pin number, Valid range:0 - 31
 * @param[in] pinLock: pin lock configration
 *        - true:  Pin lock enabled and pin configration can be changed only after reset
 *        - false: Pin lock disabled and pin configration can be changed anytime
 * @return None
 */
static inline void GPIO_REG_SetPinLock(GPIO_Type* regBase,
                                       uint32_t pinNum,
                                       bool pinLock)
{
    regBase->PCR[pinNum] = ((regBase->PCR[pinNum] & ~GPIO_PCR_LKC_Msk) | GPIO_PCR_LKC(pinLock));
}

/*!
 * @brief Configures the pin mux function for the given pin
 *
 * @param[in] regBase: GPIO register base pointer
 * @param[in] pinNum: The pin number, Valid range:0 - 31
 * @param[in] pinmux: pin muxing slot selection
 *        - GPIO_PIN_DISABLED: Pin disabled
 *        - GPIO_PIN_MUX_GPIO: Set as GPIO
 *        - others           : See PIN_MUX table
 * @return None
 */
static inline void GPIO_REG_SetPinFunc(GPIO_Type* regBase,
                                       uint32_t pinNum,
                                       gpio_pin_mux_t pinmux)
{
    regBase->PCR[pinNum] = ((regBase->PCR[pinNum] & ~GPIO_PCR_MUX_Msk) | GPIO_PCR_MUX(pinmux));
}

/*!
 * @brief Configures the pin drive strength for the given pin
 *
 * @param[in] regBase: GPIO register base pointer (GPIOA, GPIOB, GPIOC, etc.)
 * @param[in] pinNum: The pin number, Valid range:0 - 31
 * @param[in] pinDrvStrength: Pin drive strength configration
 *        - GPIO_PIN_DRV_5MA:  Pin drive strength set to 5mA
 *        - GPIO_PIN_DRV_20MA: Pin drive strength set to 20mA
 * @return None
 */
static inline void GPIO_REG_SetPinDriveStrength(GPIO_Type* regBase,
                                                uint32_t pinNum,
                                                gpio_pin_drv_t pinDrvStrength)
{
    regBase->PCR[pinNum] = ((regBase->PCR[pinNum] & ~GPIO_PCR_DSC_Msk) | GPIO_PCR_DSC(pinDrvStrength));
}

/*!
 * @brief Configures the slew rate for the given pin
 *
 * @param[in] regBase: GPIO register base pointer (GPIOA, GPIOB, GPIOC, etc.)
 * @param[in] pinNum: The pin number, Valid range:0 - 31
 * @param[in] slewRate: The slew rate configuration
 *        - GPIO_PIN_SLEW_RATE_FAST: Fast slew rate
 *        - GPIO_PIN_SLEW_RATE_SLOW: Slow slew rate
 * @return None
 */
static inline void GPIO_REG_SetPinSlewRate(GPIO_Type* regBase,
                                           uint32_t pinNum,
                                           gpio_pin_slew_rate_t slewRate)
{
    regBase->PCR[pinNum] = ((regBase->PCR[pinNum] & ~GPIO_PCR_SRC_Msk) | GPIO_PCR_SRC(slewRate));
}

/*!
 * @brief Configures the GPIO pin external interrupt for the given pin
 *
 * @param[in] regBase: GPIO register base pointer
 * @param[in] pinNum: The pin number, Valid range:0 - 31
 * @param[in] intConfig: interrupt configuration
 *        - GPIO_PIN_INT_DISABLED: Interrupt request disabled
 *        - GPIO_PIN_INT_RISING_EDGE: Interrupt on rising edge
 *        - GPIO_PIN_INT_FALLING_EDGE: Interrupt on falling edge
 *        - GPIO_PIN_INT_BOTH_EDGE: Interrupt on either edge
 * @return None
 */
static inline void GPIO_REG_SetPinInt(GPIO_Type* regBase,
                                      uint32_t pinNum,
                                      gpio_pin_int_t intConfig)
{
    regBase->PCR[pinNum] = ((regBase->PCR[pinNum] & ~GPIO_PCR_IRQT_Msk) | GPIO_PCR_IRQT(intConfig));
}

/*!
 * @brief Gets the current GPIO pin external interrupt configuration for the given pin
 *
 * @param[in] regBase: GPIO register base pointer
 * @param[in] pinNum: The pin number, Valid range:0 - 31
 * @return  interrupt configuration
 *        - PORT_INT_DISABLED: Interrupt request disabled
 *        - PORT_INT_RISING_EDGE: Interrupt on rising edge
 *        - PORT_INT_FALLING_EDGE: Interrupt on falling edge
 *        - PORT_INT_EITHER_EDGE: Interrupt on either edge
 */
static inline gpio_pin_int_t GPIO_REG_GetPinInt(const GPIO_Type* regBase,
                                                uint32_t pinNum)
{
    uint32_t pinInt = regBase->PCR[pinNum];
    pinInt = ((pinInt & GPIO_PCR_IRQT_Msk) >> GPIO_PCR_IRQT_Pos);

    return (gpio_pin_int_t)pinInt;
}

/*!
 * @brief Gets the pin external interrupt flag for the given pin
 *
 * @param[in] regBase: GPIO register base pointer
 * @return the current port pin interrupt status flags
 *         - false: interrupt is not detected
 *         - true: interrupt is detected
 */
static inline bool GPIO_REG_GetPinIntFlag(const GPIO_Type* regBase,
                                          uint32_t pinNum)
{
    uint32_t pinIntFlag = regBase->IRQF;
    pinIntFlag = ((pinIntFlag >> pinNum) & 0x01U);

    return (bool)pinIntFlag;
}

/*!
 * @brief Clears the individual pin external interrupt flag for the given pin
 *
 * @param[in] regBase: GPIO register base pointer
 * @param[in] pinNum: The pin number, Valid range:0 - 31
 * @return None
 */
static inline void GPIO_REG_ClearPinIntFlag(GPIO_Type* regBase,
                                            uint32_t pinNum)
{
    regBase->IRQF = GPIO_IRQF_IRQF(1UL << pinNum);
}

/*!
 * @brief Gets the entire port external interrupt flag for the given port
 *
 * @param[in] regBase: GPIO register base pointer
 * @return all 32 pin interrupt status flags. For specific bit:
 *         - 0: interrupt is not detected
 *         - 1: interrupt is detected
 */
static inline uint32_t GPIO_REG_GetPortIntFlag(const GPIO_Type* regBase)
{
    return (regBase->IRQF);
}

/*!
 * @brief Clears the entire port external interrupt flag for the given port
 *
 * @param[in] regBase: GPIO register base pointer
 * @return None
 */
static inline void GPIO_REG_ClearPortIntFlag(GPIO_Type* regBase)
{
    regBase->IRQF = GPIO_IRQF_IRQF_Msk;
}

/*!
 * @brief Configures digital filter for port with given configuration
 *
 * @param[in] regBase:  GPIO register base pointer
 * @param[in] width: digital filter width
 * @return None
 */
#if (CONFIG_GPIO_SUPPORT_INDEPENDENT_DFL == 1U)
static inline void GPIO_REG_SetDigitFltrRUNWidth(GPIO_Type* regBase,
                                                 uint8_t width)
{
    regBase->DFL = ((regBase->DFL & ~GPIO_DFL_RMDFL_Msk) | GPIO_DFL_RMDFL(width));
}
static inline void GPIO_REG_SetDigitFltrLPWidth(GPIO_Type* regBase,
                                                uint8_t width)
{
    regBase->DFL = ((regBase->DFL & ~GPIO_DFL_LPDFL_Msk) | GPIO_DFL_LPDFL(width));
}
#else
static inline void GPIO_REG_SetDigitFltrWidth(GPIO_Type* regBase,
                                              uint8_t width)
{
    regBase->DFL = ((regBase->DFL & ~GPIO_DFL_DFL_Msk) | GPIO_DFL_DFL(width));
}
#endif

/*!
 * @brief Enables digital filter for the given digital pin in run mode
 *
 * @param[in] regBase: GPIO register base pointer
 * @param[in] pinNum: The pin number, Valid range:0 - 31
 * @return None
 */
static inline void GPIO_REG_EnablePinDigitFltrRUN(GPIO_Type* regBase,
                                                  uint32_t pinNum)
{
    regBase->PCR[pinNum] = ((regBase->PCR[pinNum] & ~GPIO_PCR_RMDFE_Msk) | GPIO_PCR_RMDFE(1U));
}

/*!
 * @brief Disables digital filter for the given digital pin in run mode
 *
 * @param[in] regBase: GPIO register base pointer
 * @param[in] pinNum: The pin number, Valid range:0 - 31
 * @return None
 */
static inline void GPIO_REG_DisablePinDigitFltrRUN(GPIO_Type* regBase,
                                                   uint32_t pinNum)
{
    regBase->PCR[pinNum] = ((regBase->PCR[pinNum] & ~GPIO_PCR_RMDFE_Msk) | GPIO_PCR_RMDFE(0U));
}

/*!
 * @brief Enables digital filter for the given digital pin in lowpower mode
 *
 * @param[in] regBase: GPIO register base pointer
 * @param[in] pinNum: The pin number, Valid range:0 - 31
 * @return None
 */
static inline void GPIO_REG_EnablePinDigitFltrLP(GPIO_Type* regBase,
                                                 uint32_t pinNum)
{
    regBase->LPDFC = (GPIO_LPDFC_LPDFE(1U) | GPIO_LPDFC_LPDFPS(pinNum));
}

/*!
 * @brief Disables digital filter for the given port in lowpower mode
 *
 * @param[in] regBase: GPIO register base pointer
 * @return None
 */
static inline void GPIO_REG_DisablePinDigitFltrLP(GPIO_Type* regBase)
{
    regBase->LPDFC = GPIO_LPDFC_LPDFE(0U);
}

/*!
 * @brief Writes all pins configured as output with the values given in
 *        the parameter pins, '0' represents LOW, '1' represents HIGH
 *
 * @param[in] regBase: GPIO register base pointer
 * @param[in] pinMsk: pin mask to be written
 *        - 0: corresponding pin is set to LOW
 *        - 1: corresponding pin is set to HIGH
 * @return None
 */
static inline void GPIO_REG_WritePins(GPIO_Type* regBase,
                                      uint32_t pinMsk)
{
    regBase->ODR = pinMsk;
}

/*!
 * @brief Gets the current output for the given port, Only pins
 *        that are configured as output will have meaningful values
 *
 * @param[in] regBase: GPIO register base pointer
 * @return GPIO outputs. Each bit represents one pin (LSB is pin 0, MSB is pin 31)
 * For each bit:
 *        - 0: corresponding pin is set to LOW
 *        - 1: corresponding pin is set to HIGH
 */
static inline uint32_t GPIO_REG_GetPinsOutput(const GPIO_Type* regBase)
{
    return (regBase->ODR);
}

/*!
 * @brief Writes pins to logic HIGH listed in parameter pins (bits that are '1')
 *        Pins corresponding to '0' will be unaffected
 *
 * @param[in] regBase: GPIO register base pointer
 * @param[in] pinMsk: Each bit represents one pin (LSB is pin 0, MSB is pin 31)
 * For each bit:
 *        - 0: corresponding pin is unaffected
 *        - 1: corresponding pin is set to HIGH
 * @return None
 */
static inline void GPIO_REG_SetPins(GPIO_Type* regBase,
                                    uint32_t pinMsk)
{
    regBase->OSR = pinMsk;
}

/*!
 * @brief Writes pins to logic LOW listed in parameter pins (bits that are '1')
 *        Pins corresponding to '0' will be unaffected
 *
 * @param[in] regBase: GPIO register base pointer
 * @param[in] pinMsk: Each bit represents one pin (LSB is pin 0, MSB is pin 31)
 * For each bit:
 *        - 0: corresponding pin is unaffected
 *        - 1: corresponding pin is set to LOW
 * @return None
 */
static inline void GPIO_REG_ResetPins(GPIO_Type* regBase,
                                      uint32_t pinMsk)
{
    regBase->ORR = pinMsk;
}

/*!
 * @brief Toggles pins output listed in parameter pins (bits that are '1')
 *        Pins corresponding to '0' will be unaffected
 *
 * @param[in] regBase: GPIO register base pointer
 * @param[in] pinMsk: Each bit represents one pin (LSB is pin 0, MSB is pin 31).
 * For each bit:
 *        - 0: corresponding pin is unaffected
 *        - 1: corresponding pin is toggled
 * @return None
 */
static inline void GPIO_REG_TogglePins(GPIO_Type* regBase,
                                       uint32_t pinMsk)
{
    regBase->OTR = pinMsk;
}

/*!
 * @brief Reads input pins for a given port, Only pins configured as input
 *        will have meaningful values
 *
 * @param[in] regBase: GPIO register base pointer
 * @return GPIO inputs. Each bit represents one pin (LSB is pin 0, MSB is pin 31)
 * For each bit:
 *        - 0: corresponding pin is read as LOW
 *        - 1: corresponding pin is read as HIGH
 */
static inline uint32_t GPIO_REG_ReadPins(const GPIO_Type* regBase)
{
    return (regBase->IDR);
}

#if defined(__cplusplus)
}
#endif

#endif /* GPIO_REG_ACCESS_H */

/*******EOF********************************************************************/
